浏览器从输入URL到页面渲染的整个流程
从输入URL到页面渲染需要Chrome浏览器的多个进程配合,所以我们先来谈谈现阶段Chrome浏览器的多进程架构。
# 一、Chrome架构
目前Chrome采用的是多进程的架构模式,可分为主要的五类进程,分别是:浏览器(Browser)主进程、 GPU 进程、网络(NetWork)进程、多个渲染进程和多个插件进程;

浏览器进程--主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
渲染进程--核心任务是将HTML、CSS 和 JavaScript转换为用户可以与之交互的网页,排版引擎Blink和JavaScript引擎V8都是运行在该进程中,默认情况下,Chrome会为每个Tab标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
GPU进程--其实,Chrome刚开始发布的时候是没有GPU进程的。而GPU的使用初衷是为了实现3D CSS的效果,只是随后网页、Chrome的UI界面都选择采用GPU来绘制,这使得GPU成为浏览器普遍的需求。最后,Chrome在其多进程架构上也引入了GPU进程。
网络进程--主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
插件进程--主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响
了解了Chrome的多进程架构,就能够从宏观上理解从输入URL到页面渲染的过程了,这个过程主要分为导航阶段和渲染阶段。
# 二、导航阶段
# Ⅰ.浏览器主进程
# 1. 用户输入URL
- 1、浏览器进程检查
url,组装协议,构成完整的url,这时候有两种情况:- 输入的是搜索内容:地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的
URL。 - 输入的是请求
URL:地址栏会根据规则,给这段内容加上协议,合成为完整的URL;
- 输入的是搜索内容:地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的
- 2、浏览器进程通过进程间通信(
IPC)把url请求发送给网络进程;
# Ⅱ.网络进程
# 2. URL请求过程
- 3、网络进程接收到
url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程;
这里涉及到浏览器与HTTP协议的缓存策略问题:https://www.cnblogs.com/AhuntSun-blog/p/12529920.html
4、准备
IP地址和端口:进行DNS解析时先查找缓存,没有再使用DNS服务器解析,查找顺序为:浏览器缓存;
本机缓存;
ISP DNS缓存;
根域名解析服务器返回顶级域名服务器
顶级域名服务器返回权限域名服务器
权限域名服务器返回域名对应的ip地址给本地解析服务器
5、等待
TCP队列:浏览器会为每个域名最多维护6个TCP连接,如果发起一个HTTP请求时,这6个TCP连接都处于忙碌状态,那么这个请求就会处于排队状态;解决方案:- 采用域名分片技术:将一个站点的资源放在多个(
CDN)域名下面。 - 升级为
HTTP2,就没有6个TCP连接的限制了;
- 采用域名分片技术:将一个站点的资源放在多个(
6、通过三次握手建立
TCP连接:TCP 报文格式

- TCP 报头中的源端口号和目的端口号与 IP 数据报中的源 IP 和目的 IP 唯一确定一条 TCP 连接,TCP 在发送数据前必须在彼此间建立连接
- 报文主要段的意思:
序号(seq):表示发送的数据字节流,确保TCP传输有序,对每个字节编号确认号(ack):期待收到对方下一个报文段的第一个数据字节的序号,当前报文段最后一个字节的序号加1即为确认号确认(ACK):当 ACK = 1时,确认号有效;当 ACK = 0时,确认号无效同步(SYN):连接建立请求时同步序号,当 SYN = 1时,表示请求连接终止(FIN):用来释放一个连接,当 FIN = 1时,表示此报文段的发送方的数据已经发送完毕,并要求释放连接
三次握手过程
建立 TCP 连接时,需要客户端和服务端共发送3个包

- 第一次握手:客户端发送请求标志 SYN = 1 和初始序号 seq = x ,请求建立连接,客户端进入 SYN-SENT 状态
- 第二次握手:服务端发送请求标志 SYN = 1,确认标志 ACK = 1,自己的序号 seq = y,客户端的确认序号 ack = x + 1,服务端进入 SYN-RCVD 状态
- 第三次握手:客户端发送确认标志 ACK = 1,自己的序号 seq = x + 1,服务端的确认序号 ack = y + 1,发送完毕后,客户端和服务端进入 ESTABLISHED(TCP 连接成功)状态,完成三次握手
为什么要第三次挥手?避免服务器等待造成资源浪费,具体原因:
如果没有最后一个数据包确认(第三次握手),
A先发出一个建立连接的请求数据包,由于网络原因绕远路了。A经过设定的超时时间后还未收到B的确认数据包。于是发出第二个建立连接的请求数据包,这次网路通畅,
B的确认数据包也很快就到达A。于是A与B开始传输数据;过了一会
A第一次发出的建立连接的请求数据包到达了B,B以为是再次建立连接,所以又发出一个确认数据包。由于A已经收到了一个确认数据包,所以会忽略B发来的第二个确认数据包,但是B发出确认数据包之后就要一直等待A的回复,而A永远也不会回复。由此造成服务器资源浪费,这种情况多了
B计算机可能就停止响应了。7、构建并发送
HTTP请求信息;HTTP 请求由请求行、请求头和请求体组成
请求行由三部分组成,请求方法、请求URL、HTTP协议版本:
GET / HTTP/1.11请求头,例如:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cache-Control: no-cache Connection: keep-alive Cookie: /* 省略cookie信息 */ Host: www.baidu.com Pragma: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.11
2
3
4
5
6
7
8
9
10请求体:当请求方法为
post时存在8、服务器端处理请求,返回网络响应;
网络响应由响应行、响应头和响应体组成
响应行由HTTP协议版本、状态码、状态描述组成:
HTTP/1.1 200 OK1响应头包含了服务器及其返回数据的一些信息
Cache-Control: no-cache Connection: keep-alive Content-Encoding: gzip Content-Type: text/html;charset=utf-8 Date: Wed, 04 Dec 2019 12:29:13 GMT Server: apache Set-Cookie: rsv_i=f9a0SIItKqzv7kqgAAgphbGyRts3RwTg%2FLyU3Y5Eh5LwyfOOrAsvdezbay0QqkDqFZ0DfQXby4wXKT8Au8O7ZT9UuMsBq2k; path=/; domain=.baidu.com1
2
3
4
5
6
7响应体中存放返回的数据
9、客户端处理响应,首先检查服务器响应报文的状态码:
- 如果是
301/302表示服务器已更换域名需要重定向,这时网络进程会从响应头的Location字段里面读取重定向的地址,然后再发起新的HTTP或者HTTPS请求,跳回第4步。 - 如果是
304表示命中协商缓存,浏览器直接从缓存中读取结果。 - 如果是
200,就检查Content-Type字段,值为text/html说明是HTML文档,是application/octet-stream说明是文件下载;
- 如果是

10、请求结束,当通用首部字段
Conection不是Keep-Alive时,即不为TCP长连接时,通过四次挥手断开TCP连接:四次挥手过程
释放 TCP 连接时,需要客户端和服务端共发送4个包

- 第一次挥手:客户端发送终止标志 FIN = 1,自己的序号 seq = u,客户端进入 FIN-WAIT-1 状态
- 第二次挥手:服务端接收到客户端的连接释放报文,发送确认标志 ACK = 1,自己的序号 seq = v,客户端的确认号 ack = u + 1,服务端进入 CLOSE-WAIT 状态。这时候处于半关闭状态,客户端已经不再发送数据了,但是服务端若要发送数据,客户端依然要接收
- 第三次挥手:客户端收到服务端的确认结果后,进入 FIN-WAIT-2 状态。服务端发送 终止标志 FIN = 1,确认标志 ACK = 1,自己的序号 seq = w,客户端的确认号 ack = u + 1,服务端进入 LAST-ACK(最后确认)状态,等待客户端的确认
- 第四次挥手:客户端接收到服务端的连接释放报文,发送确认标志 ACK = 1,自己的序号 seq = u + 1,服务端的确认号 ack = w + 1,客户端进入 TIME-WAIT(时间等待)状态。客户端必须经过2MSL(报文最大生存事件)后,才能进入 CLOSED 状态。而服务端只要接收到客户端的确认后,立即进入 CLOSED 状态
为什么连接的时候是三次握手,释放的时候是四次挥手?
- 三次握手时,服务端把 SYN 和 ACK 放在一起发送给了客户端
- 四次挥手时,服务端收到客户端的 FIN 报文时,仅仅表示客户端不再发送数据了但是还能接收数据,必须等到所有的报文都发送完毕了,才能发送 FIN,因此先发一个 ACK 表示已经收到客户端的 FIN,延迟一段时间再发送 FIN
为什么客户端最后还要等待2MSL?
- 客户端需要保证最后一次发送的 ACK 报文到达服务端,如果服务端未收到,可以请求客户端重新发送,这样客户端还有时间重发,重启 2MSL 计时
- 1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端
- 1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达
# 3. 准备渲染进程
- 11、准备渲染进程:浏览器进程检查当前
url是否与之前打开了渲染进程的页面的根域名相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程;
# 4. 提交文档
12、提交文档:
- 渲染进程准备好后,浏览器向渲染进程发起“提交文档”的消息,渲染进程接收到消息后与网络进程建立传输数据的“管道”
- 渲染进程接收完数据后,向浏览器发送“确认提交”
- 浏览器进程接收到确认消息后更新浏览器界面状态:安全状态、地址栏
url、前进后退的历史状态、更新web页面

# 三、渲染阶段
在渲染阶段通过渲染流水线在渲染进程的主线程和合成线程配合下,完成页面的渲染;
# Ⅲ.渲染进程

渲染进程中的主线程部分
# 5. 构建DOM树
13、先将请求回来的数据解压,随后
HTML解析器将其中的HTML字节流通过分词器拆分为一个个Token,然后生成节点Node,最后解析成浏览器识别的DOM树结构。可以通过
Chrome调试工具的Console选项打开控制台输入document查看DOM树;
渲染引擎还有一个安全检查模块叫
XSSAuditor,是用来检测词法安全的。在分词器解析出来Token之后,它会检测这些模块是否安全,比如是否引用了外部脚本,是否符合CSP规范,是否存在跨站点请求等。如果出现不符合规范的内容,XSSAuditor会对该脚本或者下载任务进行拦截。
首次解析HTML时渲染进程会开启一个预解析线程,遇到HTML文档中内嵌的JavaScript和CSS外部引用就会同步提前下载这些文件,下载时间以最后下载完的文件为准。

# 6. 构建CSSOM
14、
CSS解析器将CSS转换为浏览器能识别的styleSheets也就是CSSOM:可以通过控制台输入document.styleSheets查看;这里要考虑一下阻塞的问题,由于
JavaScript有修改CSS和HTML的能力,所以,需要先等到CSS文件下载完成并生成CSSOM,然后再执行JavaScript脚本,最后再继续构建DOM。由于这种阻塞,导致了解析白屏;
优化方案:
移除
js和css的文件下载:通过内联JavaScript、内联CSS;尽量减少文件大小:如通过
webpack等工具移除不必要的注释,并压缩js文件;将不进行
DOM操作或CSS样式修改的JavaScript标记上async或者defer异步引入;使用媒体查询属性:将大的
CSS文件拆分成多个不同用途的CSS文件,只有在特定的场景下才会加载特定的CSS文件。
可以通过浏览器调试工具的Network面板中的DOMContentLoaded查看最后生成DOM树所需的时间;

# 7. 样式计算
- 15、转换样式表中的属性值,使其标准化。如
em->px,red->#ff0000,bold->700等等; - 16、计算
DOM树中每个节点的具体样式,这里遵循CSS的继承和层叠规则;可以通过Chrome调试工具的Elements选项的Computed查看某一标签的最终样式;

# 8. 布局阶段
- 17、创建布局树,遍历
DOM树中的所有节点,去掉所有隐藏的节点(比如head,添加了display:none的节点),只在布局树中保留可见的节点。 - 18、计算布局树中节点的坐标位置(较复杂,这里不展开);

# 9. 分层
- 19、对布局树进行分层,并生成分层树(
Layer Tree),可以通过Chrome调试工具的Layer选项查看。分层树中每一个节点都直接或间接的属于一个图层(如果一个节点没有对应的层,那么这个节点就从属于父节点的图层)
# 10. 图层绘制
- 20、将图层的绘制拆分成一个个绘制指令,然后将这些指令按顺序组合成一个绘制列表。
渲染进程中的合成线程部分

# 11. 切分图块
- 21、合成线程将图层切分成大小固定的图块(
256x256或者512x512)然后优先绘制靠近视口的图块,这样就可以大大加速页面的显示速度;

# Ⅳ.GPU进程
# 12. 栅格化操作
- 22、在光栅化线程池中将图块转换成位图,通常这个过程都会使用
GPU来加速生成,使用GPU生成位图的过程叫快速栅格化,或者GPU栅格化,生成的位图被保存在GPU内存中。

# Ⅴ.浏览器主进程
# 13. 合成与显示
- 23、合成:一旦所有图块都被光栅化,合成线程就会将它们合成为一张图片,并生成一个绘制图块的命令——“
DrawQuad”,然后将该命令提交给浏览器进程。
注意了:合成的过程是在渲染进程的合成线程中完成的,不会影响到渲染进程的主线程执行;
- 24、显示:浏览器进程里面有一个叫
viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
到这里,经过这一系列的阶段,编写好的HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面了。